﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MultiAnnotatedRegex.tree;
using MultiAnnotatedRegex.tree.premade;

namespace MultiAnnotatedRegex.parser {
	class Parser {
		private IEnumerator<Token> stream;

		public Tree Parse(IEnumerable<Token> tokens) {
			stream = tokens.GetEnumerator();
			Tree tree = ParseUntil(TokenType.End);
			if (stream.MoveNext()) throw new ParseException("Element encountered after end", stream.Current.Pos);
			return tree;
		}

		private void Accept(TokenType type) {
			if (stream.Current.Type != type) {
				string message = String.Format("expected {0}, found {1}", type.ToString(), stream.Current.Type.ToString());
				throw new ParseException(message, stream.Current.Pos);
			}
			stream.MoveNext();
		}

		private Tree ParseUntil(TokenType end) {
			ConcatTree result = new ConcatTree();
			while (stream.Current.Type != end) {
				result.Children.Add(ParseNext(result));
			}
			Accept(end);
			return result;
		}

		private IEnumerable<char> LexemesUntil(TokenType end) {
			while (stream.Current.Type != end) {
				yield return stream.Current.Lexeme;
				stream.MoveNext();
			}
			Accept(end);
		}

		private Tree ParseNext(ConcatTree parent) {
			switch (stream.Current.Type) {
				case TokenType.Comma:
				case TokenType.Literal:
					return new LiteralTree() { Literal = stream.Current.Lexeme.ToString() };
				case TokenType.Plus:
					return CreateRepetitionFromPrev(parent, 1, -1);
				case TokenType.Star:
					return CreateRepetitionFromPrev(parent, 0, -1);
				case TokenType.Question:
					return CreateRepetitionFromPrev(parent, 0, 1);
				case TokenType.Or:
					VerifyContentBefore(parent, stream.Current);
					Tree prev = parent.PopLastChild();
					Tree next = ParseNext(parent);
					return new OneOfTree() { Children = new List<Tree>() { prev, next } };
				case TokenType.Escape:
					stream.MoveNext();
					char escaped = stream.Current.Lexeme;
					switch (escaped) {
						case 'd': return new AnyDigitTree();
						case 'p':
							Accept(TokenType.LeftCurly);
							StringBuilder sb = new StringBuilder();
							foreach (var c in LexemesUntil(TokenType.RightCurly)) {
								sb.Append(c);
							}
							return new CharacterClassTree() { Class = sb.ToString() };
						default:
							return new LiteralTree() { Literal = escaped.ToString() };
					}
				case TokenType.LeftParen:
					return ParseUntil(TokenType.RightParen);
				case TokenType.LeftBrace:
					OneOfTree result = new OneOfTree();
					foreach (var c in LexemesUntil(TokenType.RightBrace)) {
						result.Children.Add(new LiteralTree() { Literal = c.ToString() });
					}
					return result;
				case TokenType.LeftCurly:
					throw new NotImplementedException("Explicitly numbered bounds are not supported");
				case TokenType.Dot:
					return new AnyTree();
				default:
					throw new ParseException("cannot handle token: " + stream.Current.Lexeme, stream.Current.Pos);
			}
		}

		private Tree CreateRepetitionFromPrev(ConcatTree parent, int min, int max) {
			VerifyContentBefore(parent, stream.Current);
			Tree prev = parent.PopLastChild();
			return new RepetitionTree() { Inner = prev, Min = min, Max = max };
		}

		private void VerifyContentBefore(ConcatTree parent, Token token) {
			if (parent.Children.Count == 0) throw new ParseException("missing content before '" + token.Type.ToString() + "'", token.Pos);
		}
	}
}
